

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

import weka.classifiers.trees.tdit.*;
import java.util.Enumeration;
import java.util.Vector;
import weka.classifiers.AbstractClassifier;
import weka.core.Instances;
import weka.core.Instance;
import weka.core.AdditionalMeasureProducer;
import weka.core.Capabilities;
import weka.core.Capabilities.Capability;
import weka.core.NoSupportForMissingValuesException;
import weka.classifiers.trees.tdit.TDIDTTree;
import weka.classifiers.trees.tdit.Criteria;
import weka.classifiers.trees.tdit.RandomCriteria;
import weka.classifiers.trees.tdit.FirstCriteria;
import weka.classifiers.trees.tdit.InfoGainCriteria;
import weka.classifiers.trees.tdit.InfoGainRatioCriteria;

//import weka.classifiers.trees.tdidt.RandomCriteria;
import weka.core.*;

/**
 *
 * @author mgomez
 */
public class TDIDTAlgorithm extends AbstractClassifier implements OptionHandler, 
                                          AdditionalMeasureProducer, Drawable {
    /** for serialization */
    static final long serialVersionUID = -1635481717888488888L;
    
   /**
    * Opciones para los criterios de seleccion
    */
   public static final int RANDOM = 0;
   public static final int FIRST = 1;
   public static final int INFOGAIN = 2;
   public static final int INFOGAINRATIO = 3;
   public static final int GINIINDEX = 4;
   static final String[] STRING_SPLIT_CRITERIA = {"Random", "First", "InfoGain",
      "InfoGainRation", "GiniIndex"};
   public static final Tag[] TAGS_SPLIT_CRITERIA = {
      new Tag(RANDOM, STRING_SPLIT_CRITERIA[0]),
      new Tag(FIRST, STRING_SPLIT_CRITERIA[1]),
      new Tag(INFOGAIN, STRING_SPLIT_CRITERIA[2]),
      new Tag(INFOGAINRATIO, STRING_SPLIT_CRITERIA[3]),
      new Tag(GINIINDEX, STRING_SPLIT_CRITERIA[4]),};
   /**
    * Dato miembro para almacenar las instancias a partir de las que se
    * aprende: conjunto de entrenamiento
    */
   private Instances trainingSet;
   /**
    * Dato miembro para almacenar el arbol que constituye el resultado del
    * aprendizaje
    */
   private TDIDTTree tree;
   /**
    * El algoritmo funcionara siempre con un determinado criterio de seleccion
    */
   private int idCriteria = RANDOM;
   private Criteria criteria = new RandomCriteria();

   /**
    * Class constructor
    */
   public TDIDTAlgorithm() {
      System.out.println("Valor de m_Debug: " + m_Debug);
      this.m_Debug = false;
   }

   /**
    * Construye clasificador TDIDT
    *
    * @param data datos de entrenamiento
    * @exception Exception si no puede construirse
    */
   public void buildClassifier(Instances data) throws Exception {

      // Comprueba si el algoritmo puede manejar los datos, de
      // acuerdo a las capacidades declaradas para el mismo
      getCapabilities().testWithFail(data);

      // Se crea un nuevo conjunto de datos para posteriormente
      // eliminar las instancias con datos perdidos en la clase
      data = new Instances(data);

      // Se eliminan instancias con datos perdidos en la clase
      data.deleteWithMissingClass();

      // Se construye el árbol en sí
      tree = new TDIDTTree();

      // Se llama al metodo que realmente construye el arbol: a
      // implementar en la clase TDIDTTree. De momento tiene
      // una implementacion minima para usar el criterio de
      // seleccion y asignar la clase mayoritaria en cada rama
      tree.makeTree(data, criteria);
   }

   /**
    * Clasifica una instancia de acuerdo con el arbol hecho
    *
    * @param instancia a ser clasificada
    * @return clasificacion
    * @throws NoSupportForMissingValuesException si hay valores perdidos para
    * alguno de los atributos
    */
   @Override
   public double classifyInstance(Instance instance)
           throws NoSupportForMissingValuesException {

      // Si hay valores perdidos para alguno de los atributos, 
      // se lanza la excepcion
      if (instance.hasMissingValue()) {
         throw new NoSupportForMissingValuesException(
                 "TDIDT: no missing values, "
                 + "please.");
      }

      /**
       * Se llama al metodo correspondiente de la clase Tree, que es quien de
       * verdad hara la clasificacion
       */
      return tree.classifyInstance(instance);
   }

   /**
    * Indica las capacidades del metodo. Aqui indicaremos que puede tratar con
    * valores perdidos en la clase, ya que se eliminan antes de empezar a
    * trabajar. Puede trabajar con atributos nominales
    *
    * @return las capacidades del algoritmo
    */
   @Override
   public Capabilities getCapabilities() {
      // Se obtienen las capacidades de la clase base, para
      // desactivarlas todas
      Capabilities result = super.getCapabilities();
      result.disableAll();

      // Se indica que puede trabajar con atributos nominales
      result.enable(Capability.NOMINAL_ATTRIBUTES);
      result.enable(Capability.NOMINAL_CLASS);

      // Puede tratarse con valores perdidos en la clase
      result.enable(Capability.MISSING_CLASS_VALUES);

      // Se fija numero minimo de instancias
      result.setMinimumNumberInstances(0);

      // Se devuelve el resultado
      return result;
   }

   /**
    * Devuelve un array indicando las opciones actualmente seleccionadas para el
    * funcionamiento del algoritmo. En nuestro caso la unica opcion disponible
    * se refiere al criterio de seleccion de atributos (split criteria), que se
    * identifica por SC
    *
    * @return array de opciones seleccionadas
    */
   @Override
   public String[] getOptions() {
      String[] options = new String[4];
      int current = 0;
      System.out.println("Ejecutado metodo getOptions.........");
      System.out.println("Valor de m_Debug: " + m_Debug);

      // La opcion de split criteria se identifica por -SC
      // Inicialmente se selecciona el criterio por defecto, que
      // esta almacenado en el dato miembro idCriteria
      options[current] = "-SC";
      current++;
      options[current] = STRING_SPLIT_CRITERIA[this.idCriteria];
      current++;

      while (current < options.length) {
         options[current++] = "";
      }

      for (int i = 0; i < current; i++) {
         System.out.print(options[i] + "  ");
      }
      System.out.println();
      System.out.println("Saliendo de getOptions......");

      // Devuelve las opciones
      return options;
   }

   /**
    * Este metodo se llamara una vez seleccionadas las opciones en la interfaz y
    * termina llamando al metodo los metodos que fijan los valores de los datos
    * miembro idCriteria y criteria
    *
    * @param options lista de opciones como array de cadenas
    * @exception Exception si la opcion no estaba soportada
    */
   @Override
   public void setOptions(String[] options) throws Exception {
      System.out.println("Ejecutado metodo setOptions.........");

      // Se obtienen las opciones seleccionadas del array de opciones
      String convertList = Utils.getOption("SC", options);

      // Se compara la opcion seleccionada con los posibles valores de
      // criterio. Cuando se encuentra el elegido se llama al metodo
      // setSplitMetric para que fije el valor del dato miembro correspondiente
      for (int i = 0; i < STRING_SPLIT_CRITERIA.length; i++) {
         if (convertList.compareTo(STRING_SPLIT_CRITERIA[i]) == 0) {
            setSplitCriteria(new SelectedTag(i, TAGS_SPLIT_CRITERIA));
            break;
         }
      }
   }

   /**
    * Compone una lista con las opciones disponibles, para informar a los
    * usuarios
    *
    * @return un enumerado con las opciones disponibles
    */
   @Override
   public Enumeration listOptions() {
      Vector<Option> newVector = new Vector<Option>();

      System.out.println("Ejecutado metodo listOptions.........");

      // Agrega las opciones correspondientes
      newVector.addElement(new Option(
              "\tSplit Criteria (RANO, First, InfoGain, InfoGainRatio, GiniIndex)\n"
              + "\t(default Random)",
              "SC", 1, "-SC <value>"));

      // Devuelve el vector de opciones
      return newVector.elements();
   }

   /**
    * Metodo que devuelve el valor de la opcion, mediante el correspondiente tag
    *
    * @return SelectedTag
    */
   public SelectedTag getSplitCriteria() {
      System.out.println("Ejecutado metodo getSplitCriteria.........");
      return new SelectedTag(this.idCriteria, TAGS_SPLIT_CRITERIA);
   }

   /**
    * Metodo privado para fijar el valor de los datos miembro idCriteria y
    * criteria
    *
    * @param newmetric
    */
   public void setSplitCriteria(SelectedTag newmetric) {
      System.out.println("Ejecutado metodo setSplitCriteria.........");
      if (newmetric.getTags() == TAGS_SPLIT_CRITERIA) {
         this.idCriteria = newmetric.getSelectedTag().getID();
         System.out.println("Criterio de SPLIT: " + idCriteria);
         switch (idCriteria) {
            case INFOGAIN:
               criteria = new InfoGainCriteria();
               System.out.println("InfoGain");
               break;
            case GINIINDEX:
               criteria = new GiniIndexCriteria();
               System.out.println("Indice GINI");
               break;
            case INFOGAINRATIO:
               criteria = new InfoGainRatioCriteria();
               System.out.println("Info gain ration");
               break;
            case RANDOM:
               // Se implementa este a modo de ejemplo. Los demas quedan
               // como ejercicio para vosotros
               criteria = new RandomCriteria();
               System.out.println("Random");
               break;
            case FIRST:
               criteria = new FirstCriteria();
               System.out.println("First");
               break;
         }
      }
   }

   /**
    * Devuelve el tipo de grafo que representa el arbol
    *
    * @return Drawable.TREE
    */
   @Override
   public int graphType() {
      return Drawable.TREE;
   }   
   
   /**
    * Devuelve el grafico que representa al arbol
    *
    * @return descripcion del arbol
    * @throws Exception si no no puede dar representacion del arbol
    */
   @Override
   public String graph() throws Exception {
           
      // Llama al metodo que da representacion del arbol
      // en la clase TDIDTTree
      if (tree == null){
         throw new Exception("TDIDTTree: Arbol por construir");
      }
      
      // Aqui se almacena la salida
      StringBuffer resultBuff = new StringBuffer();
      
      // Se llama al metodo toGraph de la clase TDIDTTree, que es el
      // que ofrece la descripion del arbol
      tree.toGraph(resultBuff, 0, null);
      String result = "digraph Tree {\n" + "edge [style=bold]\n" + 
                                 resultBuff.toString() + "\n}\n";
      
      // Return graph
      return result;
   }
   
  /**
   * Devuelve un enumerado con las medidas adicionales obtenidas
   * @return enumerado
   */
  public Enumeration enumerateMeasures() {
    Vector newVector = new Vector(3);
    newVector.addElement("measureTreeSize");
    newVector.addElement("measureNumLeaves");
    newVector.addElement("measureNumRules");
    return newVector.elements();
  }

  /**
   * Devuelve el valor de la correspondiente medida pedida. El metodo
   * se limita a llamar al metodo correspondiente de la clase TDIDTTree
   * @param measure nombre de la medida a obtener
   * @return valor de la medida correspondiente
   */
  public double getMeasure(String measure) {
    if (measure.compareToIgnoreCase("measureNumRules") == 0) {
      return tree.measureNumRules();
    } else if (measure.compareToIgnoreCase("measureTreeSize") == 0) {
      return tree.measureTreeSize();
    } else if (measure.compareToIgnoreCase("measureNumLeaves") == 0) {
      return tree.measureNumLeaves();
    } else {
      throw new IllegalArgumentException(measure 
			  + " not supported (j48)");
    }
  }   
   
  /**
   * Ofrece representación en forma de texto del contenido
   * del arbol
   * 
   * @return cadena de texto
   */
  public String toString() {
    if (tree == null) {
      return "TDIDT No model built yet.";
    }
    return "TDIDT\n\n" + tree.toString(0);
  }   
}
